<?php

namespace Gql\Core;


use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\NodeList;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\ScalarType;

class ParseInfo
{
    protected $info;
    protected $context;

    public function __construct(Context $context, ResolveInfo $info)
    {
        $this->info = $info;
        $this->context = $context;
    }

    /**
     * 将 ResolveInfo 向 Context 的解析
     * @return Context
     */
    public function parse(): Context
    {
        $info = $this->info;
        $context = $this->context;
        //请求字段名
        $context->setFieldName($info->fieldName);
        //请求的类型名称
        $context->setTypeName($info->parentType->name);

        $returnType = $info->returnType;
        if ($returnType instanceof ScalarType || $returnType instanceof NonNull) {
            // 标量字段
            $context->setIsScalar(true);
            return $context;
        }
        $context->setIsScalar(false);
        // Type 总字段解析
        $fields = $returnType instanceof ListOfType ? $returnType->ofType->getFields() : $returnType->getFields();
        $res = $this->parseFields($fields);
        $scalarFields = $res['scalarFields'];
        //当前类型的标量字段组
        $context->setScalarFields($scalarFields);
        //当前类型的非标量字段组
        $context->setOtherFields($res['otherFields']);
        $withs = $res['withs'];

        // 用户查询字段解析
        $fieldNodes = $info->fieldNodes[0]->selectionSet->selections;
        $res = $this->parseFieldNodes($fieldNodes, $withs, $info->fragments ?? []);

        $context->setWiths($withs);
        //用户查询的标量字段组
        $context->setSelectFields($res['selectFields']);
        //用户查询的非标量字段组
        $context->setSelectOtherFields($res['selectOtherFields']);

        $dbFields = array_filter($res['selectFields'], function ($item) use ($scalarFields) {
            return in_array($item, $scalarFields);
        });
        $context->setFields($dbFields);
        return $context;

    }

    /**
     * Type的所有字段解析
     * @param array $fields
     * @param bool $end
     * @return array
     */
    protected function parseFields(array $fields, $end = false)
    {
        $scalarFields = [];
        $otherFields = [];
        $withs = [];

        foreach ($fields as $definition) {
            $types = $definition->getType();
            if ($types instanceof ScalarType) {
                $scalarFields[] = $definition->name;
            } elseif ($end) {
                continue;
            } else {
                $otherFields[] = $definition->name;
                $directives = $definition->astNode->directives;
                // 非标量字段指令解析
                if ($directives->count()) {
                    foreach ($directives as $directive) {
                        // 关联指令 @with
                        if ($directive->name->value === 'with') {
                            $with = new With($definition->name);
                            $arguments = $this->NodeListToArray($directive->arguments);
                            if (isset($arguments['name'])) {
                                $with->setWithName($arguments['name']);
                            }
                            $fieldsArr = $types instanceof ListOfType ? $types->ofType->getFields() : $types->getFields();
                            $res = $this->parseFields($fieldsArr, true);
                            $with->setFieldNodes($res['scalarFields']);
                            $withs[$definition->name] = $with;
                        } elseif ($directive->name->value === 'data') {
                            // 存在分页（方便设置分页指定查询字段）
                            $fieldsArr = $types instanceof ListOfType ? $types->ofType->getFields() : $types->getFields();
                            $res = $this->parseFields($fieldsArr);
                            $scalarFields = $res['scalarFields'];
                            $withs = array_merge($withs, $res['withs']);
                        }

                    }
                }
            }
        }

        return [
            'scalarFields' => $scalarFields,
            'otherFields' => $otherFields,
            'withs' => $withs,
        ];
    }

    /**
     * 解析用户查询的字段
     * @param NodeList $fieldNodes
     * @param array $withs
     * @param array $fragments
     * @return array
     */
    protected function parseFieldNodes(NodeList $fieldNodes, array $withs, array $fragments)
    {
        $withFields = [];
        $selectFields = [];
        $selectOtherFields = [];

        foreach ($fieldNodes as $fieldNode) {
            //一些用户的指令等额外操作均可在此处解析
            $name = $fieldNode->name->value;
            $selectionSet = null;
            if ($fieldNode instanceof FragmentSpreadNode) {
                $Nodes = $fragments[$name]->selectionSet->selections;
                $res = $this->parseFieldNodes($Nodes, $withs, $fragments);
                $selectFields = array_merge($selectFields, $res['selectFields']);
            } elseif ($fieldNode instanceof FieldNode) {
                $selectionSet = $fieldNode->selectionSet;

                if (!is_null($selectionSet)) {
                    $selectOtherFields[] = $name;
                    if (isset($withs[$name])) {
                        $res = $this->parseFieldNodes($selectionSet->selections, $withs, $fragments);
                        $withs[$name]->setFields($res['withFields']);
                    }else{
                        foreach ($selectionSet->selections as $selection) {
                            $selectionName = $selection->name->value;

                            if (!isset($selection->selectionSet) || is_null($selection->selectionSet)) {
                                $selectFields[] = $selectionName;
                            } else {
                                if (isset($withs[$selectionName])) {
                                    $res = $this->parseFieldNodes($selection->selectionSet->selections, $withs, $fragments);
                                    $withs[$selectionName]->setFields($res['withFields']);
                                }
                            }
                        }
                    }
                } else {
                    $withFields[] = $name;
                    $selectFields[] = $name;
                }
            }
        }
        return [
            'withFields' => $withFields,
            'selectFields' => $selectFields,
            'selectOtherFields' => $selectOtherFields,
        ];
    }

    public function NodeListToArray(NodeList $access): array
    {
        $arr = [];
        foreach ($access as $v) {
            $arr[$v->name->value] = $v->value->value;
        }
        return $arr;
    }
}